/*
 * Python与C++类型转换特征类
 *
 * 历史:
 *    2020-02-15 姚彧  摘自FFPython.pyops_base.h
 */
#ifndef __PYTYPE_TRAITS_H_
#define __PYTYPE_TRAITS_H_

#include "python_config.h"
#include <structmember.h>

#include <string>
#include <vector>
#include <list>
#include <set>
#include <map>
#include <stdexcept>

#define PyString_Check PyUnicode_Check
#define PyString_AsString(pvalue_)  PyUnicode_AsUTF8(pvalue_)
#define PyString_AsStringAndSize(pvalue_, pDestPtr, nLen)  *pDestPtr = PyUnicode_AsUTF8AndSize(pvalue_, nLen)
#define PyString_FromStringAndSize PyUnicode_FromStringAndSize
#define PyString_FromString PyUnicode_FromString

template<class T>
struct pytype_traits_t
{
    static PyObject* pyobj_from_cppobj(const char* const val_)
    {
        return PyString_FromString(val_);
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, char*& ret)
    {
        return -1;
    }
    static const char* get_typename() { return "std::string";}
};

template<>
struct pytype_traits_t<long>
{
    static PyObject* pyobj_from_cppobj(long val_)
    {
        return PyLong_FromLong(val_);
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, long& ret)
    {
		if (pvalue_ == NULL)
			return -1;
		if (true == PyLong_Check(pvalue_))
        {
            ret = PyLong_AsLong(pvalue_);
            return 0;
        }
        return -1;
    }
    static const char* get_typename() { return "long";}
};

#define IMPL_INT_CODE(X) \
template<> \
struct pytype_traits_t<X> \
{ \
    static PyObject* pyobj_from_cppobj(X val_) \
    { \
        return PyLong_FromLong(long(val_)); \
    } \
    static int pyobj_to_cppobj(PyObject *pvalue_, X& ret) \
    { \
		if (pvalue_ == NULL)\
			return -1;\
		if (true == PyLong_Check(pvalue_)) \
        { \
            ret = (X)PyLong_AsLong(pvalue_); \
            return 0; \
        } \
        return -1; \
    } \
    static const char* get_typename() { return #X;} \
};

IMPL_INT_CODE(int)
IMPL_INT_CODE(unsigned int)
IMPL_INT_CODE(short)
IMPL_INT_CODE(unsigned short)
IMPL_INT_CODE(char)
IMPL_INT_CODE(unsigned char)

#ifdef _WIN32
IMPL_INT_CODE(unsigned long)
IMPL_INT_CODE(int64_t)
#else
#ifndef __x86_64__
IMPL_INT_CODE(int64_t)
#endif
IMPL_INT_CODE(uint64_t)
#endif

template<>
struct pytype_traits_t<PyObject*>
{
    static PyObject* pyobj_from_cppobj(PyObject* val_)
    {
        return val_;
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, PyObject*& ret)
    {
        ret = pvalue_;
        return 0;
    }
    static const char* get_typename() { return "PyObject";}
};

template<>
struct pytype_traits_t<const char* const>
{
    static PyObject* pyobj_from_cppobj(const char* const val_)
    {
        return PyString_FromString(val_);
    }
    static const char* get_typename() { return "std::string";}
};

template<>
struct pytype_traits_t<char*>
{
    static PyObject* pyobj_from_cppobj(char* val_)
    {
        return PyString_FromString(val_);
    }
    static const char* get_typename() { return "std::string";}
};

template<>
struct pytype_traits_t<bool>
{
    static PyObject* pyobj_from_cppobj(bool val_)
    {
        if(val_)
            Py_RETURN_TRUE;
        Py_RETURN_FALSE;
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, bool& ret)
    {
		if (pvalue_ == NULL)
			return -1;
		if (Py_False ==  pvalue_|| Py_None == pvalue_)
        {
            ret = false;
        }
        else
        {
            ret = true;
        }
        return 0;
    }
    static const char* get_typename() { return "bool";}
};

template<>
struct pytype_traits_t<std::string>
{
    static PyObject* pyobj_from_cppobj(const std::string& val_)
    {
        return PyString_FromStringAndSize(val_.c_str(), val_.size());
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, std::string& ret)
    {
		if (pvalue_ == NULL)
			return -1;
		if (true == PyString_Check(pvalue_))
        {
            const char* pDest = NULL;
            Py_ssize_t  nLen    = 0;
            PyString_AsStringAndSize(pvalue_, &pDest, &nLen);
            ret.assign(pDest, nLen);
            return 0;
        }
        return -1;
    }
    static const char* get_typename() { return "std::string";}
};

template<>
struct pytype_traits_t<float>
{
    static PyObject* pyobj_from_cppobj(float val_)
    {
        return PyFloat_FromDouble(double(val_));
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, float& ret)
    {
		if (pvalue_ == NULL)
			return -1;
		if (true == PyFloat_Check(pvalue_))
        {
            ret = (float)PyFloat_AsDouble(pvalue_);
            return 0;
        }
        return -1;
    }
    static const char* get_typename() { return "float";}
};

template<>
struct pytype_traits_t<double>
{
    static PyObject* pyobj_from_cppobj(double val_)
    {
        return PyFloat_FromDouble(val_);
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, double& ret)
    {
		if (pvalue_ == NULL)
			return -1;
		if (true == PyFloat_Check(pvalue_))
        {
            ret = PyFloat_AsDouble(pvalue_);
            return 0;
        }
        return -1;
    }
    static const char* get_typename() { return "double";}
};

template<class T>
struct pytype_traits_t<std::vector<T> >
{
    static PyObject* pyobj_from_cppobj(const std::vector<T>& val_)
    {
        PyObject* ret = PyList_New(val_.size());
        int i=0;
        for(auto& v:val_)
        {
            PyList_SetItem(ret, i++, pytype_traits_t<T>::pyobj_from_cppobj(v));
        }
        return ret;
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, std::vector<T>& ret)
    {
        ret.clear();
		if (pvalue_ == NULL)
			return -1;
		if (true == PyTuple_Check(pvalue_))
        {
            int64_t n = PyTuple_Size(pvalue_);
            T v;
            for (int64_t i = 0; i < n; ++i)
            {
                if (pytype_traits_t<T>::pyobj_to_cppobj(PyTuple_GetItem(pvalue_, i), v))
                {
                    return -1;
                }
                ret.push_back(v);
            }
            return 0;
        }
        else if (true == PyList_Check(pvalue_))
        {
            int64_t n = PyList_Size(pvalue_);
            T v;
            for (int64_t i = 0; i < n; ++i)
            {
                if (pytype_traits_t<T>::pyobj_to_cppobj(PyList_GetItem(pvalue_, i), v))
                {
                    return -1;
                }
                ret.push_back(v);
            }
            return 0;
        }
        return -1;
    }
    static const char* get_typename() { return "std::vector"; }
};

template<class T>
struct pytype_traits_t<std::list<T> >
{
    static PyObject* pyobj_from_cppobj(const std::list<T>& val_)
    {
        PyObject* ret = PyList_New(val_.size());
        int i=0;
        for(auto &v: val_)
        {
            PyList_SetItem(ret, i++, pytype_traits_t<T>::pyobj_from_cppobj(v));
        }
        return ret;
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, std::vector<T>& ret)
    {
        ret.clear();
		if (pvalue_ == NULL)
			return -1;
		if (true == PyTuple_Check(pvalue_))
        {
            int64_t n = PyTuple_Size(pvalue_);
            T v;
            for (int64_t i = 0; i < n; ++i)
            {
                if (pytype_traits_t<T>::pyobj_to_cppobj(PyTuple_GetItem(pvalue_, i), v))
                {
                    return -1;
                }
                ret.push_back(v);
            }
            return 0;
        }
        else if (true == PyList_Check(pvalue_))
        {
            int64_t n = PyList_Size(pvalue_);
            T v;
            for (int64_t i = 0; i < n; ++i)
            {
                if (pytype_traits_t<T>::pyobj_to_cppobj(PyList_GetItem(pvalue_, i), v))
                {
                    return -1;
                }
                ret.push_back(v);
            }
            return 0;
        }
        return -1;
    }
    static const char* get_typename() { return "std::list"; }
};

template<typename T>
struct pytype_traits_t<std::set<T> >
{
    static PyObject* pyobj_from_cppobj(const std::set<T>& val_)
    {
        PyObject* ret = PySet_New(NULL);
        for (auto &p:val_)
        {
            PyObject *v = pytype_traits_t<T>::pyobj_from_cppobj(p);
            PySet_Add(ret, v);
            Py_DECREF(v);
        }
        return ret;
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, std::set<T>& ret)
    {
        ret.clear();
		if (pvalue_ == NULL)
			return -1;
		PyObject *iter = PyObject_GetIter(pvalue_);
        PyObject *item = NULL;
        T tmp;
        while (NULL != iter && NULL != (item = PyIter_Next(iter)))
        {
            if (pytype_traits_t<T>::pyobj_to_cppobj(item, tmp))
            {
                Py_DECREF(item);
                Py_DECREF(iter);
                return -1;
            }
            ret.insert(tmp);
            Py_DECREF(item);
        }
        if (iter)
        {
            Py_DECREF(iter);
            return 0;
        }
        return -1;
    }
    static const char* get_typename() { return "std::set";}
};

template<typename T, typename R>
struct pytype_traits_t<std::map<T, R> >
{
    static PyObject* pyobj_from_cppobj(const std::map<T, R>& val_)
    {
        PyObject* ret = PyDict_New();
        for (auto it = val_.begin(); it != val_.end(); ++it)
        {
            PyObject *k = pytype_traits_t<T>::pyobj_from_cppobj(it->first);
            PyObject *v = pytype_traits_t<R>::pyobj_from_cppobj(it->second);
            PyDict_SetItem(ret, k, v);
            Py_DECREF(k);
            Py_DECREF(v);
        }
        return ret;
    }
    static int pyobj_to_cppobj(PyObject *pvalue_, std::map<T, R>& ret)
    {
        ret.clear();
		if (pvalue_ == NULL)
			return -1;
		if (true == PyDict_Check(pvalue_))
        {
            PyObject *key = NULL, *value = NULL;
            Py_ssize_t pos = 0;

            while (PyDict_Next(pvalue_, &pos, &key, &value))
            {
                T tmp_key;
                R tmp_value;
                if (pytype_traits_t<T>::pyobj_to_cppobj(key, tmp_key) ||
                    pytype_traits_t<R>::pyobj_to_cppobj(value, tmp_value))
                {
                    return -1;
                }
                ret[tmp_key] = tmp_value;
            }
            return 0;
        }
        return -1;
    }
    static const char* get_typename() { return "std::map";}
};

#endif